﻿using System;
using System.Collections.Generic;

namespace Tuxedo.Compile
{
	internal abstract class Expression
	{
		public Expression()
		{
			this.EffectiveValue = this;
		}

		public virtual Expression EffectiveValue { get; set; }

		private static readonly OpParser[] ORDER_OF_OPERAIONS = new OpParser[] {
			new AssignmentParser(),
			new TernaryParser(),
			new NullCoalescingParser(),
			new ConditionalParser(),
			new BitwiseParser(),
			new EqualityParser(),
			new InequalityParser(),
			new BitShiftParser(),
			new AdditiveParser(),
			new MultiplicativeParser(),
			new UnaryParser()
		};

		private static int unaryParserIndex;

		static Expression()
		{
			for (int i = 0; i < ORDER_OF_OPERAIONS.Length; ++i)
			{
				if (i < ORDER_OF_OPERAIONS.Length - 1)
				{
					ORDER_OF_OPERAIONS[i].Next = ORDER_OF_OPERAIONS[i + 1];
				}

				if (ORDER_OF_OPERAIONS[i] is UnaryParser)
				{
					unaryParserIndex = i;
				}
			}
		}

		public static Expression ParseExpression(TokenStream tokens)
		{
			return Expression.ParseExpression(tokens, false);
		}

		public static Expression ParseExpression(TokenStream tokens, bool failSilently)
		{
			return ORDER_OF_OPERAIONS[0].Parse(tokens, failSilently);
		}

		public static Expression ParseUnaryExpression(TokenStream tokens, bool failSilently)
		{
			return ORDER_OF_OPERAIONS[unaryParserIndex].Parse(tokens, failSilently);
		}

		private abstract class OpParser
		{
			public OpParser Next { get; set; }
			public abstract Expression Parse(TokenStream tokens, bool failSilently);
			public Expression ParseNext(TokenStream tokens, bool failSilently)
			{
				if (this.Next == null)
				{
					if (failSilently) return null;
					// unexpected token perhaps?
					throw new Exception();
				}

				return this.Next.Parse(tokens, failSilently);
			}

			protected Expression Fail(string message, bool failSilently)
			{
				if (failSilently) return null;
				throw new Exception(message);
			}
		}

		private class AssignmentParser : OpParser
		{
			private static readonly HashSet<string> ASSIGNMENT_OPS = new HashSet<string>(
				"= += -= *= /= %= <<= >>= |= &= ^=".Split(' '));

			public override Expression Parse(TokenStream tokens, bool failSilently)
			{
				Expression left = this.Next.Parse(tokens, failSilently);

				string maybeOp = tokens.PeekValue();
				if (maybeOp != null && ASSIGNMENT_OPS.Contains(maybeOp))
				{
					tokens.Pop();
					Expression right = Expression.ParseExpression(tokens, failSilently);
					return new AssignmentExpression(left, right, maybeOp);
				}
				return left;
			}
		}

		private class NullCoalescingParser : OpParser
		{
			public override Expression Parse(TokenStream tokens, bool failSilently)
			{
				Expression left = this.ParseNext(tokens, failSilently);
				if (tokens.PopIfPresent("??"))
				{
					Expression right = Expression.ParseExpression(tokens, failSilently);
					return new NullCoalescingExpression(left, right);
				}
				return left;
			}
		}

		private class TernaryParser : OpParser
		{
			public override Expression Parse(TokenStream tokens, bool failSilently)
			{
				Expression left = this.ParseNext(tokens, failSilently);
				if (tokens.PopIfPresent("?"))
				{
					Expression ifTrue = Expression.ParseExpression(tokens, failSilently);
					tokens.PopExpected(":");
					Expression ifFalse = Expression.ParseExpression(tokens, failSilently);
					return new TernaryExpression(left, ifTrue, ifFalse);
				}
				return left;
			}
		}

		private class ConditionalParser : SimpleOpParser
		{
			public ConditionalParser() : base("&& ||".Split(' ')) { }
			protected override Expression CreateThing(List<Expression> expressions, List<string> ops)
			{
				return new ConditionalExpression(expressions, ops);
			}
		}

		private class BitwiseParser : SimpleOpParser
		{
			public BitwiseParser() : base("| & ^".Split(' ')) { }
			protected override Expression CreateThing(List<Expression> expressions, List<string> ops)
			{
				return new BitwiseExpression(expressions, ops);
			}
		}

		private class EqualityParser : SimpleOpParser
		{
			public EqualityParser() : base("== !=".Split(' ')) { }
			protected override Expression CreateThing(List<Expression> expressions, List<string> ops)
			{
				return new EqualityExpression(expressions, ops);
			}
		}

		private class InequalityParser : SimpleOpParser
		{
			public InequalityParser() : base("< > <= >= is as".Split(' ')) { }
			protected override Expression CreateThing(List<Expression> expressions, List<string> ops)
			{
				return new InequalityExpression(expressions, ops);
			}
		}

		private class BitShiftParser : OpParser
		{
			public BitShiftParser() : base() { }
			public override Expression Parse(TokenStream tokens, bool failSilently)
			{
				Expression left = this.ParseNext(tokens, failSilently);
				if (left == null)
				{
					if (failSilently) return null;
					throw new Exception("Unexpected token");
				}

				List<Expression> expressions = new List<Expression>() { left };
				List<string> ops = new List<string>();
				while (true)
				{
					bool found = false;
					if (tokens.PopIfPresent("<<"))
					{
						ops.Add("<<");
						found = true;
					}
					else if (tokens.IsNext(">"))
					{
						int tokenIndex = tokens.GetCurrentIndex();
						tokens.Pop();
						Token token = tokens.Pop();
						if (token != null && token.Value == ">" && token.PreviousIsGreaterThanCharacterHACK)
						{
							found = true;
							ops.Add(">>");
						}
						else
						{
							tokens.RestoreIndex(tokenIndex);
						}
					}

					if (found)
					{
						Expression next = this.ParseNext(tokens, failSilently);
						if (next == null)
						{
							if (failSilently) return null;
							throw new Exception("Expected expression");
						}
						expressions.Add(next);
					}
					else
					{
						if (expressions.Count > 1)
						{
							return new BitShiftExpression(expressions, ops);
						}
						return expressions[0];
					}
				}
			}
		}

		private class AdditiveParser : SimpleOpParser
		{
			public AdditiveParser() : base("+ -".Split(' ')) { }
			protected override Expression CreateThing(List<Expression> expressions, List<string> ops)
			{
				return new AdditiveExpression(expressions, ops);
			}
		}

		private class MultiplicativeParser : SimpleOpParser
		{
			public MultiplicativeParser() : base("* / %".Split(' ')) { }
			protected override Expression CreateThing(List<Expression> expressions, List<string> ops)
			{
				return new MultiplicativeExpression(expressions, ops);
			}
		}

		private class UnaryParser : OpParser
		{
			private static readonly HashSet<string> PREFIX_TOKENS = new HashSet<string>("++ -- ! ~ + -".Split(' '));

			public override Expression Parse(TokenStream tokens, bool failSilently)
			{
				string next = tokens.PeekValue();
				if (PREFIX_TOKENS.Contains(next))
				{
					tokens.Pop();
					Expression expression = this.Parse(tokens, failSilently);
					switch (next)
					{
						case "++":
						case "--":
							return new IncrementExpression(expression, next == "++", true);

						case "!":
							return new NotExpression(expression);

						case "~":
							return new BitNotExpression(expression);

						case "+":
						case "-":
							return new SignPrefixExpression(expression, next == "-");

						default:
							break;
					}
				}

				Expression root = null;

				if (tokens.PopIfPresent("new"))
				{
					UnverifiedType type = UnverifiedType.Parse(tokens, false, true);
					
					if (type.IsArray)
					{
						if (tokens.PopIfPresent("{"))
						{
							// TODO: need a unit test creating an array of wrong size for explicitly defined items
							List<Expression> items = new List<Expression>();

							bool nextDisallowed = false;

							while (!tokens.PopIfPresent("}"))
							{
								if (nextDisallowed) throw new Exception("Unexpected expression. Expected } or ,");

								Expression item = Expression.ParseExpression(tokens);
								items.Add(item);
								nextDisallowed = !tokens.PopIfPresent(",");
							}

							if (type.ArraySize != null)
							{
								// TODO: put off enforcing the size of the array until a later pass because constants can be used.
							}

							return new ArrayCreation(type, null, items);
						}
						else if (type.ArraySize != null)
						{
							return new ArrayCreation(type, type.ArraySize.EffectiveValue, null);
						}
						else
						{
							throw new Exception("Must declare a size or items in an array");
						}
					}
					else
					{
						tokens.PopExpected("(");
						List<Expression> parameters = new List<Expression>();
						while (!tokens.PopIfPresent(")"))
						{
							if (parameters.Count > 0)
							{
								tokens.PopExpected(",");
							}
							Expression constructorParam = Expression.ParseExpression(tokens);
							parameters.Add(constructorParam);
						}

						List<Expression> values = null;
						List<Expression> keys = null;
						// constructor also has initialization values, e.g. new List<int> foo = new List<int>() { 1, 2, 3[,] };
						if (tokens.PopIfPresent("{"))
						{
							values = new List<Expression>();
							keys = new List<Expression>();
							
							bool mustEnd = false;
							while (!tokens.PopIfPresent("}"))
							{
								if (mustEnd)
								{
									tokens.PopExpected(","); // mustEnd is true when the next token is not a comma so this will throw with the correct expected message.
								}

								if (tokens.PopIfPresent("{"))
								{
									// dictionary style declaration
									keys.Add(Expression.ParseExpression(tokens));
									tokens.PopExpected(",");
									values.Add(Expression.ParseExpression(tokens));
									tokens.PopExpected("}");
								}
								else
								{
									keys.Add(null);
									values.Add(Expression.ParseExpression(tokens)); // will pick up assignment expressions
								}

								mustEnd = !tokens.PopIfPresent(",");
							}
						}

						root = new ConstructorCall(type, parameters, keys, values);
					}
				}
				else
				{
					// parenthesis
					if (tokens.IsNext("("))
					{
						Cast cast = Cast.TryParse(tokens);
						if (cast != null) return cast;

						// just a normal parenthesis
						tokens.PopExpected("(");
						root = Expression.ParseExpression(tokens, failSilently);
						tokens.PopExpected(")");
					}

					if (root == null)
					{
						root = ConstantParser.Parse(tokens);
					}

					if (root == null)
					{
						if (tokens.PopIfPresent("typeof"))
						{
							tokens.PopExpected("(");
							UnverifiedType type = UnverifiedType.Parse(tokens, false);
							tokens.PopExpected(")");
							root = new TypeOfFunction(type);
						}
					}

					if (root == null)
					{
						string variableName = tokens.PopIdentifier(failSilently);
						if (variableName == null) return null;
						root = new Variable(variableName);
					}
				}

				// TODO: disallow suffixes for some inline constant values such as numbers and booleans. Yeah, that's it, I think. Oh, and null. Can't have that.
				bool suffixesAllowed = !(
					root is FloatConstant ||
					root is DoubleConstant ||
					root is IntegerConstant ||
					root is BooleanConstant ||
					root is NullConstant);

				// suffixes
				if (suffixesAllowed)
				{
					while (true)
					{
						if (tokens.PopIfPresent("."))
						{
							root = new StepAccess(root, tokens.PopIdentifier());
						}
						else if (tokens.PopIfPresent("["))
						{
							root = new IndexAccess(root, Expression.ParseExpression(tokens, failSilently));
							tokens.PopExpected("]");
						}
						else if (tokens.IsNext("<"))
						{
							int startTokenIndex = tokens.GetCurrentIndex();
							tokens.PopExpected("<");
							List<UnverifiedType> types = new List<UnverifiedType>() { UnverifiedType.Parse(tokens, true) };
							if (types[0] == null)
							{
								tokens.RestoreIndex(startTokenIndex);
								return root;
							}

							while (tokens.PopIfPresent(",")) {
								UnverifiedType type = UnverifiedType.Parse(tokens, true);
								if (type == null) {
									tokens.RestoreIndex(startTokenIndex);
									return root;
								}
								types.Add(type);
							}

							if (!tokens.PopIfPresent(">"))
							{
								tokens.RestoreIndex(startTokenIndex);
								return root;
							}
							tokens.PopExpected("(");
							List<string> outOrRef = new List<string>();
							List<Expression> parameters = new List<Expression>();
							if (!Expression.ParseParameters(tokens, outOrRef, parameters, true))
							{
								tokens.RestoreIndex(startTokenIndex);
								return root;
							}
							tokens.PopExpected(")");
							root = new FunctionCall(root, types, parameters, outOrRef);
						}
						else if (tokens.PopIfPresent("("))
						{
							List<Expression> parameters = new List<Expression>();
							List<string> outOrRef = new List<string>();
							Expression.ParseParameters(tokens, outOrRef, parameters, failSilently);
							tokens.PopExpected(")");
							root = new FunctionCall(root, null, parameters, outOrRef);
						}
						else
						{
							break;
						}
					}

					if (tokens.PopIfPresent("++"))
					{
						root = new IncrementExpression(root, true, false);
					}
					else if (tokens.PopIfPresent("--"))
					{
						root = new IncrementExpression(root, false, false);
					}
				}

				return root;
			}
		}

		private static bool ParseParameters(TokenStream tokens, IList<string> outOrRef, IList<Expression> parameters, bool failSilently)
		{
			while (!tokens.IsNext(")"))
			{
				if (parameters.Count > 0)
				{
					if (!tokens.PopIfPresent(","))
					{
						if (failSilently) return false;
						tokens.PopExpected(","); // throws
					}
				}

				outOrRef.Add(tokens.PopAnyOf("out", "ref"));
				Expression expr = Expression.ParseExpression(tokens, failSilently);
				if (expr == null)
				{
					return false;
				}
				parameters.Add(expr);
			}

			return true;
		}

		private abstract class SimpleOpParser : OpParser
		{
			private HashSet<string> opTokens;
			public SimpleOpParser(ICollection<string> opTokens)
			{
				this.opTokens = new HashSet<string>(opTokens);
			}

			public override Expression Parse(TokenStream tokens, bool failSilently)
			{
				Expression left = this.ParseNext(tokens, failSilently);
				string next = tokens.PeekValue();
				if (this.opTokens.Contains(next))
				{
					List<Expression> expressions = new List<Expression>() { left };
					List<string> ops = new List<string>();
					do
					{
						next = tokens.Pop().Value;
						ops.Add(next);
						expressions.Add(this.ParseNext(tokens, failSilently));
						next = tokens.PeekValue();
					} while (this.opTokens.Contains(next));

					return this.CreateThing(expressions, ops);
				}
				return left;
			}

			protected abstract Expression CreateThing(List<Expression> expressions, List<string> ops);
		}

		protected void SimplifyConstantValues(IList<Expression> expressions, IList<string> ops)
		{
			while (this.MaybeSimplifyConstantValues(expressions, ops)) { }
			if (ops.Count == 0)
			{
				this.EffectiveValue = expressions[0];
			}
		}

		private bool MaybeSimplifyConstantValues(IList<Expression> expressions, IList<string> ops)
		{
			if (ops.Count > 0)
			{
				Constant left = expressions[0].EffectiveValue as Constant;
				Constant right = expressions[1].EffectiveValue as Constant;
				if (left != null && right != null)
				{
					expressions[1] = this.DoConstantSimplification(left, right, ops[0]);
					expressions.RemoveAt(0);
					ops.RemoveAt(0);
					return true;
				}
			}
			return false;
		}

		protected virtual Expression DoConstantSimplification(Constant left, Constant right, string op)
		{
			throw new Exception("This codepath is not allowed.");
		}
	}
}
